import { useEffect, useMemo } from 'react' import classNames from 'classnames' import { Field, Form, Formik, useFormikContext } from 'formik' import { trpc, inferQueryOutput, useQueryClient } from '~/utils/trpc' import IVInputField from '~/components/IVInputField' import IVButton from '~/components/IVButton' import PageHeading from '~/components/PageHeading' import { useHasPermission } from '~/components/DashboardContext' import IconRocket from '~/icons/compiled/Rocket' import IconOnline from '~/icons/compiled/Online' import IconOffline from '~/icons/compiled/Offline' import { DialogStateReturn } from 'reakit' import IVDialog, { useDialogState } from '~/components/IVDialog' import EmptyState from '~/components/EmptyState' import { hostStatusToString } from '~/utils/text' import relativeTime from '~/utils/date' import IVSpinner from '~/components/IVSpinner' import { isUrl } from '~/utils/url' import SimpleTable from '~/components/SimpleTable' import useTable, { IVTableRow } from '~/components/IVTable/useTable' import IconRefresh from '~/icons/compiled/Refresh' function HttpHostList({ hosts, }: { hosts: inferQueryOutput<'http-hosts.list'> }) { const queryClient = useQueryClient() const checkHost = trpc.useMutation('http-hosts.check', { onSuccess(host) { queryClient.setQueryData( ['http-hosts.list'], (hosts: inferQueryOutput<'http-hosts.list'> | undefined) => { if (!hosts?.length) return [host] return hosts.map(h => (h.id === host.id ? host : h)) } ) }, }) const deleteHost = trpc.useMutation('http-hosts.delete', { onSuccess(host) { queryClient.setQueryData( ['http-hosts.list'], (hosts: inferQueryOutput<'http-hosts.list'> | undefined) => { if (!hosts) return [] return hosts.filter(h => h.id !== host.id) } ) }, }) const data = useMemo(() => { return hosts.map((host, idx) => ({ key: String(idx), data: { status: (
{host.status === 'ONLINE' ? ( ) : ( )} {hostStatusToString(host.status)}
), url: {host.url}, updatedAt: (
{relativeTime(host.updatedAt)}
), actions: ( ), }, })) }, [hosts, checkHost, deleteHost]) const table = useTable({ data, columns: ['Status', 'URL', 'Last checked', ''], }) return (
) } function AddHostDialog({ onSubmit, dialog, }: { onSubmit?: () => void dialog: DialogStateReturn }) { const addHost = trpc.useMutation('http-hosts.add', { onSuccess() { dialog.hide() if (onSubmit) { onSubmit() } }, }) const isHidden = !dialog.visible && !dialog.animating return ( initialValues={{ url: '', }} onSubmit={async ({ url }) => { if (addHost.isLoading) return addHost.mutate({ url, }) }} > {({ touched, errors }) => (
{ if (!isUrl(value)) { return 'Please enter a valid URL.' } }} /> {addHost.isError && (

Sorry, there was a problem adding the host.

{addHost.error?.data?.code === 'NOT_FOUND' && (

Are you sure a serverless endpoint is available at that URL?

)} {addHost.error?.data?.code === 'BAD_REQUEST' && (

Is there already an endpoint configured at this URL?

)}
)}
)}
) } function ResetFormToken({ isResetReady }: { isResetReady: boolean }) { const { resetForm } = useFormikContext() useEffect(() => { if (isResetReady) resetForm() }, [resetForm, isResetReady]) return null } export default function HostEndpointsPage() { useHasPermission('READ_PROD_ACTIONS', { redirectToDashboardHome: true }) const canAddHost = useHasPermission('WRITE_PROD_ACTIONS') const addHostDialog = useDialogState() const hosts = trpc.useQuery(['http-hosts.list']) return (
{ addHostDialog.show() }, }, ] : undefined } /> {hosts.data ? ( hosts.data.length ? ( ) : ( { addHostDialog.show() }, }, { label: 'Documentation', theme: 'secondary', href: '/docs/deployments/serverless', absolute: true, }, ] : undefined } >

Serverless endpoints are an alternative way to host actions.

Instead of a persistent connection, these endpoints listen at a URL and are spawned on demand, making them suitable for serverless workflows.

) ) : ( )}
) }